#include "stochastic_number.hpp"
#include <iostream>
#include <iomanip>
#include <random>
#include <vector>
#include <bitset>
#include <cstdint>
#include <ctime>


typedef int64_t nanotime_t;
nanotime_t nanotime() {
  static const nanotime_t G = 1000000000;
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  return (ts.tv_nsec + G*ts.tv_sec);
}


class NanoTimer {
public:
  NanoTimer()
    : m_startTime(0),
    m_stopTime(0) {
  }

  void start() {
    m_stopTime = 0;
    m_startTime = nanotime();
  }
  void stop() {
    m_stopTime = nanotime();
  }
  nanotime_t elapsed() const {
    if (m_startTime == 0) {
      return 0;
    }
    if (m_stopTime == 0) {
      return (nanotime() - m_startTime);
    }
    return (m_stopTime - m_startTime);
  }

private:
  nanotime_t m_startTime;
  nanotime_t m_stopTime;
};


template <class Number, size_t iters = 1000>
class ArithmeticPerformanceTest {
public:
  template <class Operation>
  nanotime_t test(Operation const& op) {
    std::mt19937 gen(nanotime());
    nanotime_t time = 0;
    for (size_t i = 0; i < iters; ++i) {
      Number a(Number(std::generate_canonical<long double, 10>(gen)));
      Number b(Number(std::generate_canonical<long double, 10>(gen)));
      time += performOperation(op, a, b);
    }
    return time;
  }

private:
  template <class Operation>
  nanotime_t performOperation(Operation const& op, Number &a, Number &b) {
    static NanoTimer timer;
    timer.start();
    op(a, b);
    timer.stop();
    return timer.elapsed();
  }
};


template <class Number>
struct Multiplication {
  void operator() (Number & a, Number & b) const {
    a *= b;
  }
};

const size_t iters = 200000l;
const size_t G = 1000000000l;

template <class Number, size_t iters>
size_t do_test(ArithmeticPerformanceTest<Number, iters> & test) {
  return G * (static_cast<double>(iters) / test.test(Multiplication<Number>()));
}


typedef experiment::stochastic8192_t testtype;
typedef experiment::stochastic_number<8192, std::bitset<8192>> testtype2;

int main(int argc, char* argv[]) {
  ArithmeticPerformanceTest<double, iters> doubleTest;
  std::cout << "double                                                   "
      << std::setw(16) << do_test(doubleTest) << " op/sec" << std::endl;
  ArithmeticPerformanceTest<testtype, iters> stochTest;
  std::cout << "stochastic_number<8192, experiment::bitset<8192>>        "
      << std::setw(16) << do_test(stochTest) << " op/sec" << std::endl;
  ArithmeticPerformanceTest<testtype2, iters> stochTest2;
  std::cout << "stochastic_number<8192, std::bitset<8192>>               "
      << std::setw(16) << do_test(stochTest2) << " op/sec" << std::endl;
}

